package com.tangyuan.servicegateway.filter;

import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import com.tangyuan.common.constant.RedisConstant;
import com.tangyuan.common.result.Result;
import com.tangyuan.common.result.ResultCodeEnum;
import com.tangyuan.model.User;
import com.tangyuan.servicegateway.config.GlobalDebounceConfiguration;
import com.tangyuan.servicegateway.config.GlobalPassUriConfiguration;
import feign.Request;
import lombok.Data;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Data
@Component
public class GatewayGlobleFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(GatewayGlobleFilter.class);
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private GlobalPassUriConfiguration globalPassUri;
    @Autowired
    private GlobalDebounceConfiguration debounceConfiguration;

    //放行Url
    private  final List<String> urlList = Arrays.asList(
            "/user/wx/authRedirect",
            "/user/alipay/authRedirect",
            "/user/setting/info/checkVersion",
            "/user/setting/info/checkForceUpdate",
            "/user/userActivity/getHonouredGuestByNumber",
            "/user/userActivity/getHonouredGuestByVoteNum",
            "/user/userActivity/getVoteById"
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().getPath();
        // 先进行接口防抖处理 2024/4/27 上午10:34 By Wesin
        // 获取客户端IP 获取代理之前的IP，nginx代理之后原IP存在这些请求头
        String[] proxyHeaders = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
        String ip = null;
        for (String header : proxyHeaders) {
            ip = request.getHeaders().getFirst(header);
            if (!NetUtil.isUnknown(ip)) {
                ip = NetUtil.getMultistageReverseProxyIp(ip);
                break;
            }
        }
        if (StringUtils.isBlank(ip)) {
            ip = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
        }
        // 记录IP+请求uri 2024/4/27 上午10:46 By Wesin
        String debounceKey = RedisConstant.DEBOUNCE_KEY_PREFIX + ip + ":" + uri;
        if (StringUtils.isNotBlank(ip)) {
            // 根据get/post请求获取请求参数，作为key，用来判断请求是否完全一致 2024/4/27 下午4:03 By Wesin
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            String paramsString = JSONUtil.toJsonStr(queryParams);
            String value = (String) redisTemplate.opsForValue().get(debounceKey);
            // 存在一秒内的请求记录，返回请求频繁 2024/4/27 上午10:49 By Wesin
            if (StringUtils.isNotBlank(value) && value.equals(paramsString)) {
                String result = JSONUtil.toJsonStr(JSONUtil.parseObj(Result.build(null, ResultCodeEnum.REQUEST_DEBOUNCE), false));
                byte[] bytes = result.getBytes(StandardCharsets.UTF_8);
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                HttpHeaders responseHeaders = exchange.getResponse().getHeaders();
                if (!responseHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
                    // 设置你希望的Content-Type
                    responseHeaders.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
                }
                return exchange.getResponse().writeWith(Flux.just(buffer));
            }
            redisTemplate.opsForValue().set(debounceKey, paramsString, debounceConfiguration.getTime(), debounceConfiguration.getTimeUnit());
        }
        // 路由不是空，并且是由/loginUser开头，同时不是退出登录接口，直接放行 2024/4/15 下午4:16 By Wesin
        if (StringUtils.isNotBlank(uri)
                && (uri.startsWith("/loginUser") || uri.startsWith("/xxl-job-admin"))
                && !"/loginUser/signOut".equalsIgnoreCase(uri)) {
            return chain.filter(exchange);
        }
        if (urlList.contains(uri)) {
            return chain.filter(exchange);
        }
        HttpHeaders headers = request.getHeaders();
        // 先从请求头获取token，没有再去query获取 2024/4/15 上午9:01 By Wesin
        String token = headers.getFirst(HttpHeaders.AUTHORIZATION);
        if (token == null) {
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            token = queryParams.getFirst(HttpHeaders.AUTHORIZATION);
        }
        // 全局放行，有token就解析，这里判断没有也放行 2024/4/22 下午3:32 By Wesin
        if (StringUtils.isEmpty(token) && globalPassUri.getGlobalPass().contains(uri)) {
            return chain.filter(exchange);
        }
        // 获取到token就解析 2024/4/22 下午3:37 By Wesin
        if (StringUtils.isNotBlank(token)) {
            token = token.replace("Bearer ", "").trim();
            User user = (User) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
            if (user == null || user.getId() == null) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                log.info("================================未获取到User================================");
                return exchange.getResponse().setComplete();
            }
            // 判断是否其他设备登录 2024/4/24 上午10:44 By Wesin
            if (user.getOtherDeviceOnline() != null && user.getOtherDeviceOnline()) {
                // 构建返回内容 2024/4/24 上午11:02 By Wesin
                String result = JSONUtil.toJsonStr(JSONUtil.parseObj(Result.build(null, ResultCodeEnum.OTHER_DEVICE_ONLINE), false));
                byte[] bytes = result.getBytes(StandardCharsets.UTF_8);
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                HttpHeaders responseHeaders = exchange.getResponse().getHeaders();
                    if (!responseHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
                        // 设置你希望的Content-Type
                        responseHeaders.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
                    }
                // 系统单设备登录，获取通知后该token无用，移除 2024/4/24 上午11:03 By Wesin
                redisTemplate.delete(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
                return exchange.getResponse().writeWith(Flux.just(buffer));
            }

            // 重新set数据，使过期时间重置 2024/4/20 下午5:09 By Wesin
            redisTemplate.opsForValue().set(RedisConstant.USER_LOGIN_KEY_PREFIX + token, user, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);
            redisTemplate.opsForValue().set(RedisConstant.USER_ONLINE_KEY_PREFIX + user.getMobile(), token, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);

            // 请求头封装用户ID
            exchange.getRequest().mutate().headers(
                    httpHeaders -> httpHeaders.add("userId", user.getId().toString())
            ).build();
            return chain.filter(exchange);
        }

        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        log.info("================================未获取到token================================");
        return exchange.getResponse().setComplete();
    }

    /**
     * 设置过滤器执行顺序，越小优先级越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }
}
